package net.worldscale.xp.wspr.impl;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import net.worldscale.xp.wspr.Console;

class ImplConsole extends Console {

    private final MyInputProxy mIn = new MyInputProxy();
    private final MyOutputProxy mOut = new MyOutputProxy();
    private final PrintStream mOutPrint;
    private final Hashtable<String, Command> mCommandSet;
    private String mTitle = "WSPR Console";

    public ImplConsole() {
        mCommandSet = new Hashtable<String, Command>();
        mOutPrint = new PrintStream(mOut);
    }

    public void init() {
        this.setInput(System.in);
        this.setOutput(System.out);
        this.registerCommand(new MyHelpCommand());
        //    this.printInputTip();
        (new Thread() {

            @Override
            public void run() {
                procRead();
            }
        }).start();
    }

    private void procRead() {
        try {
            final InputStream is = this.mIn;
            // final PrintStream os = this.mOutPrint;
            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
            for (;;) {
                final int b = is.read();
                // os.write(b); // echo
                switch (b) {
                    case 0x0d:
                    case 0x0a: {
                        try {
                            final byte[] bytes = baos.toByteArray();
                            baos.reset();
                            final String str = new String(bytes);
                            processCommandLine(str);
                            printInputTip();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        break;
                    }
                    default: {
                        baos.write(b);
                    }
                }
            }
        } catch (Exception e) {
            // e.printStackTrace();
        }
    }

    @Override
    public String getTitle() {
        return mTitle;
    }

    @Override
    public void setTitle(String title) {
        if (title != null) {
            mTitle = title;
        }
    }

    private void printInputTip() {
        this.getOutput().println();
        this.getOutput().print("WSPR: ");
    }

    @Override
    public boolean executeCommand(String cmdLine) {
        final PrintStream out = this.getOutput();
        out.println();
        out.println("Execute command: " + cmdLine);
        return this.processCommandLine(cmdLine);
    }

    private boolean processCommandLine(final String str) {
        if (str == null) {
            return false;
        }
        if (str.equals("")) {
            return false;
        }
        final MyExeContext ec = new MyExeContext(this, str);
        String name = ec.getCommandName();
        if (name == null) {
            name = "[null]";
        }
        final Command cmd = this.mCommandSet.get(name);
        if (cmd == null) {
            this.getOutput().println();
            this.getOutput().println("The command '" + name + "' is unsupported.");
            return false;
        } else {
            cmd.execute(ec);
            return true;
        }
    }

    @Override
    public PrintStream getOutput() {
        return mOutPrint;
    }

    @Override
    public void setInput(InputStream is) {
        if (is != null) {
            this.mIn.setTarget(is);
        }
    }

    @Override
    public void setOutput(OutputStream os) {
        if (os != null) {
            this.mOut.setTarget(os);
        }
    }

    @Override
    public void close() {
        try {
            this.mIn.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void registerCommand(Command cmd) {
        final String name = cmd.getName();
        this.mCommandSet.put(name, cmd);
    }

    private static final class MyCommandLineReader {

        private final char[] buffer;
        private final int length;
        private int ptr = 0;

        public MyCommandLineReader(String str) {
            buffer = str.toCharArray();
            length = buffer.length;
        }

        private int getChar() {
            if (ptr < length) {
                return buffer[ptr];
            } else {
                return -1;
            }
        }

        private int readChar() {
            if (ptr < length) {
                return buffer[ptr++];
            } else {
                return -1;
            }
        }

        private static boolean isNameChar(char ch) {
            if (('a' <= ch) && (ch <= 'z')) {
                return true;
            }
            if (('A' <= ch) && (ch <= 'Z')) {
                return true;
            }
            if (('0' <= ch) && (ch <= '9')) {
                return true;
            }
            switch (ch) {
                case '_':
                case '-':
                case '.':
                    return true;
                default:
                    return false;
            }
        }

        public String readName() {
            final StringBuffer sbuf = new StringBuffer();
            for (; isNameChar((char) getChar());) {
                int ch = readChar();
                sbuf.append((char) ch);
            }
            return sbuf.toString();
        }

        public void skipSpace() {
            for (int ch = getChar(); (ch == ' ') || (ch == 0x09); ch = getChar()) {
                readChar();
            }
        }

        public boolean isEOF() {
            return (length <= ptr);
        }

        public void readAndCheck(char ch) {
            final int ch0 = readChar();
            if (ch0 != ch) {
                throw new RuntimeException("Want a '" + ch + "' but a '" + ch0 + "' here.");
            }
        }

        public String readUntil(char ch) {
            final StringBuffer sbuf = new StringBuffer();
            for (;;) {
                final int ch2 = getChar();
                if ((ch2 < 0) || (ch2 == ch)) {
                    break;
                }
                sbuf.append((char) readChar());
            }
            return sbuf.toString();
        }
    }

    private static final class MyCommandParameter extends Parameter {

        private final String mName;
        private final String mValue;

        public MyCommandParameter(String name, String value) {
            mName = name;
            mValue = value;
        }

        @Override
        public String getName() {
            return mName;
        }

        @Override
        public String getValue() {
            return mValue;
        }
    }

    private static final class MyExeContext extends ExecuteContext {

        private final Console mConsole;
        private final String mName;
        private Vector<Parameter> mParameters = new Vector<Parameter>();

        public MyExeContext(Console console, String cmdLine) {
            mConsole = console;
            final MyCommandLineReader reader = new MyCommandLineReader(cmdLine);
            mName = reader.readName();
            for (reader.skipSpace(); !reader.isEOF(); reader.skipSpace()) {
                final String name, value;
                name = reader.readName();
                reader.readAndCheck('=');
                value = reader.readUntil('&');
                final MyCommandParameter param = new MyCommandParameter(name, value);
                mParameters.addElement(param);
                if (reader.getChar() == '&') {
                    reader.readAndCheck('&');
                }
            }
        }

        @Override
        public Console getConsole() {
            return mConsole;
        }

        @Override
        public String getCommandName() {
            return mName;
        }

        @Override
        public String getParameterValueByName(String parameterName) {
            final Enumeration<Parameter> enu = this.mParameters.elements();
            for (; enu.hasMoreElements();) {
                final Parameter p = enu.nextElement();
                if (parameterName.equals(p.getName())) {
                    return p.getValue();
                }
            }
            return null;
        }

        @Override
        public Parameter[] getParameters() {
            final Parameter[] array = new Parameter[this.mParameters.size()];
            this.mParameters.copyInto(array);
            return array;
        }
    }

    private static final class MyInputProxy extends InputStream {

        private InputStream mTarget;

        public MyInputProxy() {
        }

        public void setTarget(InputStream target) {
            if (target != null) {
                mTarget = target;
            }
        }

        @Override
        public long skip(long n) throws IOException {
            return mTarget.skip(n);
        }

        @Override
        public synchronized void reset() throws IOException {
            mTarget.reset();
        }

        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            return mTarget.read(b, off, len);
        }

        @Override
        public int read(byte[] b) throws IOException {
            return mTarget.read(b);
        }

        @Override
        public int read() throws IOException {
            return mTarget.read();
        }

        @Override
        public boolean markSupported() {
            return mTarget.markSupported();
        }

        @Override
        public synchronized void mark(int readlimit) {
            mTarget.mark(readlimit);
        }

        @Override
        public void close() throws IOException {
            mTarget.close();
        }

        @Override
        public int available() throws IOException {
            return mTarget.available();
        }
    }

    @Override
    public Command[] getCommands() {
        final Vector<Command> v = new Vector<Command>();
        v.addAll(this.mCommandSet.values());
        final Command[] ret = new Command[v.size()];
        v.copyInto(ret);
        return ret;
    }

    private static final class MyOutputProxy extends OutputStream {

        private OutputStream mTarget;

        public MyOutputProxy() {
        }

        public void setTarget(OutputStream target) {
            if (target != null) {
                mTarget = target;
            }
        }

        @Override
        public void write(byte[] b, int off, int len) throws IOException {
            mTarget.write(b, off, len);
        }

        @Override
        public void write(byte[] b) throws IOException {
            mTarget.write(b);
        }

        @Override
        public void write(int b) throws IOException {
            mTarget.write(b);
        }

        @Override
        public void flush() throws IOException {
            mTarget.flush();
        }

        @Override
        public void close() throws IOException {
            mTarget.close();
        }
    }

    private static final class MyHelpCommand extends Command {

        @Override
        public void execute(ExecuteContext ec) {
            final Console con = ec.getConsole();
            final PrintStream out = con.getOutput();
            out.println("You can control the " + con.getTitle() + " with these commands:");
            final Command[] cmds = con.getCommands();
            for (int i = 0; i < cmds.length; i++) {
                final Command cmd = cmds[i];
                out.println("    " + cmd.getName() + " - " + cmd.getHelpText());
            }
        }

        @Override
        public String getHelpText() {
            return "Show supported command & how to use it.";
        }

        @Override
        public String getName() {
            return "help";
        }
    }
}
